package cs.ubbcluj.ro.algorithm;

import cs.ubbcluj.ro.datatypes.interfaces.Matrix;
import cs.ubbcluj.ro.datatypes.interfaces.Vector;
import cs.ubbcluj.ro.linearsystem.implementations.LinearSystemLU;
import cs.ubbcluj.ro.lp.LP;

public class AlgorithmBase {
	
	LP lp;
	double epsilon;
	double mu;
	double rho;
	double sigma;
	double alphaPrimal;
	double alphaDual;
	Vector rb;
	Vector rc;
	Vector deltaX;
	Vector deltaY;
	Vector deltaS;
	Vector v;
	Vector nu;
	
	public AlgorithmBase(LP lp, double sigma, double rho, double epsilon, double theta) {
		this.lp = lp;
		this.sigma = sigma;
		this.rho = rho;
		this.epsilon = epsilon;
	}
	
	/**
	 * calculates the minimal value of the two given vectors
	 * @param v1
	 * @param v2
	 * @return
	 * @throws InterruptedException
	 */
    public double min(final Vector v1, final Vector v2) throws InterruptedException {
    	double result;
    	int i;
    	i=0;
    	while (i<v2.getNrOfRows() && (v2.get(i)>0))
    		i++;
    	if (i==v2.getNrOfRows())
    		return 1;
    	double minim = (-1) * (v1.get(i) / v2.get(i));
    	for (int j = i+1; j < v2.getNrOfRows(); j++) {    		
    		if (v2.get(j) < 0) {    			
    			result = (-1) * (v1.get(j) / v2.get(j));    			
    			if (result < minim) {
    				minim = result;    				
    			}
    		}
    	}
   		return minim;
    }
	
	/**
	 * calculates the value of 'nu' given by the formula: nu = mu - x * s
	 * abstract function which will be overwritten by extended classes
	 * @return Vector
	 * @throws InterruptedException
	 */
	public Vector calculateRightHandSide(boolean useSuperClass) throws InterruptedException {
		Vector resultVector = lp.getX().createHelperVector(lp.getXLength());
		resultVector.set(lp.getX());
		resultVector.productScalar(lp.getS());
		resultVector.product(-1);
		resultVector.sum(mu);
		return resultVector;
	}
	
	/**
	 * calculates the value of variable 'mu'
	 * formula: mu = sigma * x^T * s / n
	 * @return double
	 * @throws InterruptedException 
	 */
	public double getMu() throws InterruptedException {	
		Vector resultVector = lp.getX().createHelperVector(lp.getXLength());
		resultVector.set(lp.getX());
		resultVector.product(sigma);
		return resultVector.product(lp.getS()) / lp.getXLength();		
	}

	/**
	 * calculates deltaY using the formula: AD2At * deltaY = - (AS^-1 * nu + AXS^-1 * rc + rb)
	 * @return value of deltaY
	 * @throws InterruptedException 
	 */
	public Vector getDeltaY(Matrix AD2At, Matrix AXS_1, Matrix AS_1) throws InterruptedException {		
		// add linear system solving
		Vector b = AS_1.product(nu).sum(AXS_1.product(rc)).sum(rb);		
		LinearSystemLU linearSystem = new LinearSystemLU(AD2At, b);		
		return linearSystem.solveSystem().product(-1);
	}
	
	/**
	 * calculates deltaS using the formula: deltaS = -A^T * deltaY - rc
	 * @return value of deltaS
	 * @throws InterruptedException
	 */
	public Vector getDeltaS() throws InterruptedException {
		return lp.getA().trans().product(-1).product(deltaY).diff(rc); 
	}
	
	/**
	 * calculates deltaX using the formula: deltaX = S^-1 * (nu - X * deltaS)
	 * @return
	 * @throws InterruptedException
	 */
	public Vector getDeltaX() throws InterruptedException {			
		
		// calculate X * deltaS
		Vector helperVector = lp.getX().createHelperVector(lp.getXLength());		
		helperVector.set(lp.getX());						
		helperVector.productScalar(deltaS);		
				
		// sets the value of deltaX to S^-1
		deltaX = lp.getInvS();				
				
		// makes scalar product with mu^-1 - X * deltaS		
		Vector helperVector2 = nu.diff(helperVector);
		deltaX.productScalar(helperVector2);
		
		return deltaX;
	}
    
    /**
     * sets values for x given by the formula x = x + rho * alphaPrimal * deltaX
     * @throws InterruptedException
     */
    public void setX() throws InterruptedException {
    	Vector x = lp.getX().createHelperVector(lp.getXLength());
    	x.set(lp.getX());
    	lp.setX(x.sum(deltaX.product(rho * alphaPrimal)));
    }
    
    /**
     * sets values for y given by the formula y = y + rho * alphaDual * deltaY
     * @throws InterruptedException
     */
    public void setY() throws InterruptedException {
    	Vector y = lp.getY().createHelperVector(lp.getY().getNrOfRows());
    	y.set(lp.getY());
    	lp.setY(y.sum(deltaY.product(rho * alphaDual)));
    }
    
    /**
     * sets values for s given by the formula s = s + rho * alphaDual * deltaS
     * @throws InterruptedException
     */
    public void setS() throws InterruptedException {
    	Vector s = lp.getS().createHelperVector(lp.getS().getNrOfRows());
    	s.set(lp.getS());
    	lp.setS(s.sum(deltaS.product(rho * alphaDual)));
    }
    
    /**
     * must be overwritten by the Weighted Algorithm 
     * sets the value of w given by the formula w = (1 - theta) * w
     * @throws InterruptedException  
     */
    public void setW() throws InterruptedException {    	
    }
    
    /**
     * calculates the primal eligibility given by the formula: |Ax-b| / (1+max(x))
     * where Ax-b = rb
     * @return
     * @throws InterruptedException
     */
    public double primalFeasibility() throws InterruptedException {
    	double primalFeasibility = rb.norm() / (1 + lp.getX().infinityNorm());
    	System.out.println("primalFeasibility: " + primalFeasibility);
    	return primalFeasibility;
    }
    
    /**
     * calculates the dual eligibility given by the formula: |A^T * y + s - c| / (1+max(s))
     * where A^T * y + s - c = rc
     * @return
     * @throws InterruptedException
     */
    public double dualFeasibility() throws InterruptedException {
    	double dualFeasibility = rc.norm() / (1 + lp.getS().infinityNorm());
    	System.out.println("dualFeasibility: " + dualFeasibility);
    	return dualFeasibility;
    }
    
    /**
     * calculates the duality gap given by the formula: |c^T * x - b^T * y| / (1 + |b^T * y|)
     * @return
     * @throws InterruptedException
     */
    public double dualityGap() throws InterruptedException {
    	Vector c = lp.getC().createHelperVector(lp.getC().getNrOfRows());
    	c.set(lp.getC());
    	
    	Vector b = lp.getB().createHelperVector(lp.getB().getNrOfRows());
    	b.set(lp.getB());
    	
    	double cx = c.product(lp.getX());
    	double by = b.product(lp.getY());
    	double dualityGap = Math.abs(cx - by) / (1 + Math.abs(cx));
    	System.out.println("dualityGap: " + dualityGap);
    	return dualityGap;
    }
    
    /**
     * checks if result is good enough, so further calculations can be stopped
     * @return
     * @throws InterruptedException
     */
    public boolean isResultGoodEnough() throws InterruptedException {
    	return (primalFeasibility() <= epsilon && dualFeasibility() <= epsilon && dualityGap() <= epsilon);
    	//return (dualityGap() < epsilon);
    } 
	
	/**
	 * solves problem 
	 * @throws InterruptedException
	 */
	public void solveProblem() throws InterruptedException {	//run
		int iterationCount = 0;
		
		do {			
			// get value of mu
			mu = getMu();
						
			// get value of nu
			nu = calculateRightHandSide(false);
				
			// calculate the value of rb		
			rb = lp.getRb();
			
			// calculate the value of rc
			rc = lp.getRc();

			// calculate AXS^-1
			Matrix AXS_1 = lp.getAXS_1();
			
			// calculate AS^-1
			Matrix AS_1 = lp.getAS_1();
			
			// calculate deltaY
			Matrix AD2At = lp.getAD2At();			
			deltaY = getDeltaY(AD2At, AXS_1, AS_1);
			
			// calculate deltaS
			deltaS = getDeltaS();
			
			// calculates deltaX
			deltaX = getDeltaX();
			
			// calculates alphaPrimal
			alphaPrimal = min(lp.getX(), deltaX);
			
			// calculates alphaDual
			alphaDual = min(lp.getS(), deltaS);

			// set values for x, y, s
			setX();
			setY();
			setS();
			//setW();		
			
			iterationCount++;

			System.out.println("----------------------------");
			System.out.println("Primal = " + lp.getPrimal());
			System.out.println("Dual= " + lp.getDual());
			System.out.println("iteration = " + iterationCount);
			
		} while (!isResultGoodEnough() && iterationCount < 50);		
		
		System.out.println("\niterationCount = " + iterationCount);
	}
	
	
}
